Transformação na tabela ratings

%load_ext pretty_jupyter

Importar Bibliotecas

import pandas as pd
import os
import numpy as np
import re
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin

Carregar Arquivo

ratings_treino = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/2.Datasets_Limpeza/ratings_treino.pickle", compression="gzip")
ratings_teste = pd.read_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/2.Datasets_Limpeza/ratings_teste.pickle", compression="gzip")
ratings_treino
userId movieId rating timestamp
213 5 47 5.0 1029389303
214 5 175 4.0 1029389417
215 5 257 4.0 1029389115
216 5 318 4.0 1029389280
217 5 319 4.0 1029389327
... ... ... ... ...
33830996 330963 53953 0.5 1230144729
33830997 330963 54190 3.0 1230144915
33830998 330963 55069 5.0 1230144786
33830999 330963 55282 5.0 1230144757
33831000 330963 58293 0.5 1230144558

820508 rows × 4 columns

Classe

Classe 1: Eliminar a coluna "timestamp"

class EliminarColuna(BaseEstimator, TransformerMixin):
    def __init__(self, col_eliminar):
        self.col_eliminar = col_eliminar

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        X_transformado = X.drop(columns=self.col_eliminar)
        return X_transformado

Classe 2: Criar uma coluna com o total de avaliações por filme

class AvaliacoesFilme(BaseEstimator, TransformerMixin):
    ''' Classe que cria uma coluna com o total de avaliações por filme 
    Args:
    - coluna: nome da coluna com o número de ocorrências de cada valor (movieId)
    - X : nome da tabela (com dados de treino ou teste)

    '''
    def __init__(self, coluna):
        self.coluna = coluna

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        tabela_agrupada = X.groupby(self.coluna).size().reset_index(name="Numero_de_Avaliacoes_por_Filme")
        tabela_mesclada = pd.merge(tabela_agrupada, X, on=self.coluna)
        return tabela_mesclada

Classe 3: Criar uma coluna com o total de avaliações por usuário

class AvaliacoesUsuarios(BaseEstimator, TransformerMixin):
    ''' Classe que cria uma coluna com o total de avaliações por usuários 
    Args:
    - coluna: nome da coluna com o número de ocorrências de cada valor (userId)
    - X : nome da tabela (com dados de treino ou teste)
    '''
    def __init__(self, coluna):
        self.coluna = coluna

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        tabela_agrupada = X.groupby(self.coluna).size().reset_index(name="Numero_de_Avaliacoes_por_usuarios")
        tabela_mesclada = pd.merge(tabela_agrupada, X, on=self.coluna)
        return tabela_mesclada 

Classse 4: Criar coluna rating_medio (simples)

class MediaSimples(BaseEstimator, TransformerMixin):
    def __init__(self, col_movie, col_rating):
        self.col_movie = col_movie
        self.col_rating = col_rating

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        df_media = X.groupby(self.col_movie)[self.col_rating].mean().reset_index()
        df_media1 = df_media.rename(columns={self.col_rating:'rating_medio_simples'})
        df_media_mesclado = pd.merge(X, df_media1, on=self.col_movie)
        return df_media_mesclado

Classe 5: Criar Coluna rating_medio_ponderado

Faremos uma adaptação da fórmula da média ponderada com o objetivo de penalizar os filmes com baixo número de avaliações. A fórmula utilizada é a seguinte:

$$\large \text{rating_medio_ponderado_1} = \frac{{\text{Numero_de_Avaliações_por_Filme}\ \ \times \ \ \text{rating_medio_por_filme}}}{{\text{Numero_de_Avaliações_por_Filme}\, \ + \, \ \log(\text{Numero_de_Avaliações_por_Filme} + 1)\ \times \ \text{penalizacao}}}$$



onde,

  • 𝑅𝑎𝑡𝑖𝑛𝑔_𝑚e𝑑𝑖𝑜_𝑝𝑜𝑛𝑑𝑒𝑟𝑎𝑑𝑜_1 é a avaliação ponderada com penalização
  • 𝑁u𝑚𝑒𝑟𝑜_𝑑𝑒_𝑎𝑣𝑎𝑙𝑖𝑎çõ𝑒𝑠_𝑝𝑜𝑟_𝐹𝑖𝑙𝑚𝑒 é o total de avaliações por filme
  • R𝑎𝑡𝑖𝑛𝑔_𝑚e𝑑𝑖𝑜_𝑝𝑜𝑟_𝑓𝑖𝑙𝑚𝑒 é a média simples da avaliação de cada filme
  • log⁡(𝑁u𝑚𝑒𝑟𝑜_𝑑𝑒_𝑎𝑣𝑎𝑙𝑖𝑎çõ𝑒𝑠_𝑝𝑜𝑟_𝐹𝑖𝑙𝑚𝑒 +1) é o logaritmo natural (base e) do número de avaliações por filme + 1
    • Vamos somar log() por 1 para evitar erros com valores de log(0)
  • 𝑃𝑒𝑛𝑎𝑙𝑖𝑧𝑎cao é um fator que penaliza filmes com baixo número de avaliações de modo a diminuir sua média.

  • Quanto maior o fator de 𝑃𝑒𝑛𝑎𝑙𝑖𝑧𝑎cao, menor será a média dos filmes com pouca avaliação.
    Aplicar o logaritmo natural é uma forma de deixar a distribuição das médias menos assimétricas e mais próximas da distribuição normal. O que reduz o impacto de valores extremos na média ponderada.

    class MediaPonderada(BaseEstimator, TransformerMixin):
        ''' Classe que cria uma coluna com o rating médio ponderado '''
        def __init__(self, col_avaliacoes_filmes , col_rating_medio_simples, penalizacao):
            self.col_avaliacoes_filmes = col_avaliacoes_filmes
            self.col_rating_medio_simples = col_rating_medio_simples
            self.penalizacao = penalizacao
    
        def fit(self, X, y=None):
            return self
    
        def transform(self, X):
            fator_suavizacao = self.penalizacao
            X['log_numero_avaliacoes'] = np.log(X[self.col_avaliacoes_filmes]+1)
            X['rating_medio_ponderado'] = (X[self.col_avaliacoes_filmes]*X[self.col_rating_medio_simples]) / \
                (X[self.col_avaliacoes_filmes] + X['log_numero_avaliacoes'] * fator_suavizacao)
            X.drop(columns='log_numero_avaliacoes', inplace=True)
            return X
    

    Classe 6: Criar classe com média individual vs data

    Esta classe serve para darmos um peso maior as avalições mais recentes. Isso é importante, pois elas refletem a opinião atual do usuário.
    Para calcular do peso dado as avaliações em relação ao tempo, vamos utilizar uma função exponencial. Pois dessa forma, daremos um peso que vai aumentar/diminuir de maneira suave, sem mudanças abruptas no peso.
    A função abaixo vai realizar duas contas:

    $\text{Peso}= e^{-\text{taxa de decaimento}\times\text{diferença em dias}}$

    $\text{Rating_Times} = \text{Rating} \times \text{Peso}$

    Onde,

    • $\text{taxa de decaimento}$:controla a taxa de decaimento exponencial. Neste caso, foi definido como 0,0005.

    • $\text{diferença em dias}$: é a diferença em dias entre a data da avaliação e a data atual.

    • $\text{Rating}$: nota (avaliação) individual

    class MediaTimes(BaseEstimator, TransformerMixin):
        def __init__(self, current_date=None, decay_rate=0.0005):
            ''' Inicializando da Classe MediaTimes
            Args:
            - current_date: data atual
            - decay_rate: a taxa de decaimento exponencial (default é 0.0005).
            '''
            self.current_date = current_date if current_date else pd.to_datetime('today')
            self.decay_rate = decay_rate
    
        def fit(self, X, y=None):
            # Neste caso, fit não faz nada, pois não há parâmetros a aprender
            return self
    
        def transform(self, X, y=None):
            ''' Função que aplica a transformação em X
            Args:
            - X: DataFrame e contém as colunas 'rating' e 'timestamp'
            '''
            # Assumindo que X é um 
            X = X.copy()
            # Converter timestamp para datetime
            X['timestamp'] = pd.to_datetime(X['timestamp'], unit='s')  
            # Criar a nova coluna de rating ponderado
            X['rating_times'] = X.apply(lambda row: self.calculate_weighted_rating(row['rating'], row['timestamp']), axis=1)
            return X
    
        def calculate_weighted_rating(self, rating, date):
            ''' Função que calcula Calcula o rating ponderado
                   com base na diferença em dias entre a data da avaliação 
                   e a data atual. Ou seja, 
                  - peso = e^(decay_rate * diferença dias)
                  - Rating_Times = Rating * Peso
            '''
            # Calculando a diferença em dias
            days_diff = (self.current_date - date).days
            # Calculando o peso
            peso = np.exp(-self.decay_rate * days_diff)
            # Retornando o rating ponderado
            return rating * peso
    

    Transformação em ratings

    Criar Pipeline para Análise Exploratória

    # Criar a pipeline
    pipeline_analise = Pipeline([
        ('eliminar_timestamp',EliminarColuna(col_eliminar='timestamp')),
        ('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
        ('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
        ('media_simples', MediaSimples(col_movie='movieId', col_rating='rating')),
        ('media_ponderada', MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme', 
                                            col_rating_medio_simples='rating_medio_simples', penalizacao=50))
    ])
    
    pipeline_analise
    
    Pipeline(steps=[('eliminar_timestamp',
                     EliminarColuna(col_eliminar='timestamp')),
                    ('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
                    ('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
                    ('media_simples',
                     MediaSimples(col_movie='movieId', col_rating='rating')),
                    ('media_ponderada',
                     MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
                                    col_rating_medio_simples='rating_medio_simples',
                                    penalizacao=50))])
    In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
    On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

    Criar Pipeline para Modelagem

    pipeline_modelagem = Pipeline([
        ('rating vs times', MediaTimes()),
        ('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
        ('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
        ('media_simples', MediaSimples(col_movie='movieId', col_rating='rating')),
        ('media_ponderada', MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme', 
                                            col_rating_medio_simples='rating_medio_simples', penalizacao=50))
    ])
    
    pipeline_modelagem
    
    Pipeline(steps=[('rating vs times',
                     MediaTimes(current_date=Timestamp('2024-07-04 17:11:16.318840'))),
                    ('avaliacoes_filme', AvaliacoesFilme(coluna='movieId')),
                    ('avaliacoes_usuarios', AvaliacoesUsuarios(coluna='userId')),
                    ('media_simples',
                     MediaSimples(col_movie='movieId', col_rating='rating')),
                    ('media_ponderada',
                     MediaPonderada(col_avaliacoes_filmes='Numero_de_Avaliacoes_por_Filme',
                                    col_rating_medio_simples='rating_medio_simples',
                                    penalizacao=50))])
    In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
    On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

    Aplicar Pipeline nos dados de treino e teste

    Pipeline para Análise Exploratória

    # Usar a pipeline
    ratings_treino_transformado = pipeline_analise.fit_transform(ratings_treino)
    ratings_teste_transformado = pipeline_analise.transform(ratings_teste)
    
    ratings_treino_transformado
    
    userId Numero_de_Avaliacoes_por_usuarios movieId Numero_de_Avaliacoes_por_Filme rating rating_medio_simples rating_medio_ponderado
    0 5 43 47 1567 5.0 4.057754 3.286254
    1 5 43 175 140 4.0 3.482143 1.258266
    2 5 43 257 104 4.0 3.341346 1.032082
    3 5 43 318 2948 4.0 4.415366 3.888469
    4 5 43 319 207 4.0 3.910628 1.708250
    ... ... ... ... ... ... ... ...
    820503 330963 34 53953 126 0.5 3.361111 1.150161
    820504 330963 34 54190 74 3.0 3.371622 0.860718
    820505 330963 34 55069 28 5.0 3.875000 0.552543
    820506 330963 34 55282 77 5.0 3.298701 0.861498
    820507 330963 34 58293 91 0.5 2.659341 0.763192

    820508 rows × 7 columns

    ratings_teste_transformado
    
    userId Numero_de_Avaliacoes_por_usuarios movieId Numero_de_Avaliacoes_por_Filme rating rating_medio_simples rating_medio_ponderado
    0 128 19 168 81 3.0 3.166667 0.851209
    1 128 19 208 196 1.0 2.984694 1.271296
    2 128 19 356 682 4.0 4.016862 2.716883
    3 128 19 480 483 2.0 3.628364 2.212461
    4 128 19 590 304 2.0 3.822368 1.969439
    ... ... ... ... ... ... ... ...
    207989 330948 218 115210 37 1.0 3.891892 0.657897
    207990 330948 218 129779 2 1.5 2.750000 0.096609
    207991 330948 218 130634 26 0.5 3.288462 0.448132
    207992 330948 218 132584 1 0.5 0.500000 0.014022
    207993 330948 218 136459 1 1.5 1.500000 0.042067

    207994 rows × 7 columns

    Pipeline para Modelagem

    # Usar a pipeline
    ratings_treino_transformado_modelagem = pipeline_modelagem.fit_transform(ratings_treino)
    ratings_teste_transformado_modelagem = pipeline_modelagem.transform(ratings_teste)
    
    ratings_treino_transformado_modelagem 
    
    userId Numero_de_Avaliacoes_por_usuarios movieId Numero_de_Avaliacoes_por_Filme rating timestamp rating_times rating_medio_simples rating_medio_ponderado
    0 5 43 47 1567 5.0 2002-08-15 05:28:23 0.091853 4.057754 3.286254
    1 5 43 175 140 4.0 2002-08-15 05:30:17 0.073483 3.482143 1.258266
    2 5 43 257 104 4.0 2002-08-15 05:25:15 0.073483 3.341346 1.032082
    3 5 43 318 2948 4.0 2002-08-15 05:28:00 0.073483 4.415366 3.888469
    4 5 43 319 207 4.0 2002-08-15 05:28:47 0.073483 3.910628 1.708250
    ... ... ... ... ... ... ... ... ... ...
    820503 330963 34 53953 126 0.5 2008-12-24 18:52:09 0.029359 3.361111 1.150161
    820504 330963 34 54190 74 3.0 2008-12-24 18:55:15 0.176156 3.371622 0.860718
    820505 330963 34 55069 28 5.0 2008-12-24 18:53:06 0.293593 3.875000 0.552543
    820506 330963 34 55282 77 5.0 2008-12-24 18:52:37 0.293593 3.298701 0.861498
    820507 330963 34 58293 91 0.5 2008-12-24 18:49:18 0.029359 2.659341 0.763192

    820508 rows × 9 columns

    ratings_teste_transformado_modelagem 
    
    userId Numero_de_Avaliacoes_por_usuarios movieId Numero_de_Avaliacoes_por_Filme rating timestamp rating_times rating_medio_simples rating_medio_ponderado
    0 128 19 168 81 3.0 1998-07-28 13:16:18 0.026308 3.166667 0.851209
    1 128 19 208 196 1.0 1998-07-28 13:17:36 0.008769 2.984694 1.271296
    2 128 19 356 682 4.0 1998-07-28 13:20:20 0.035077 4.016862 2.716883
    3 128 19 480 483 2.0 1998-07-28 13:14:38 0.017539 3.628364 2.212461
    4 128 19 590 304 2.0 1998-07-28 13:11:21 0.017539 3.822368 1.969439
    ... ... ... ... ... ... ... ... ... ...
    207989 330948 218 115210 37 1.0 2016-01-07 04:58:08 0.212142 3.891892 0.657897
    207990 330948 218 129779 2 1.5 2016-01-07 04:40:17 0.318213 2.750000 0.096609
    207991 330948 218 130634 26 0.5 2016-01-07 04:33:24 0.106071 3.288462 0.448132
    207992 330948 218 132584 1 0.5 2016-01-07 04:58:03 0.106071 0.500000 0.014022
    207993 330948 218 136459 1 1.5 2016-01-07 05:26:23 0.318213 1.500000 0.042067

    207994 rows × 9 columns

    Salvar tabelas

    # Salvar tabela com dados de treino
    #ratings_treino_transformado.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_treino_transformado.pickle", compression = "gzip")
    # Salvar tabela com dados de treino
    ratings_treino_transformado_modelagem.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_treino_transformado_modelagem.pickle", compression = "gzip")
    
    # Salvar tabela com dados de teste
    #ratings_teste_transformado.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_teste_transformado.pickle", compression = "gzip")
    # Salvar tabela com dados de teste
    ratings_teste_transformado_modelagem.to_pickle("C:/0.Projetos/5.Sistema_de_Recomendacao_MovieLens_2/Datasets/3.Datasets_Transformação/3.1_Datasets_Transformação_parte_1/ratings_teste_transformado_modelagem.pickle", compression = "gzip")
    

    ⚠ Arquivos para as próximas etapas

    • ratings_treino_transformado
    • ratings_teste_transformado

    • ratings_treino_transformado_modelagem

    • ratings_teste_transformado_modelagem